package com.magima.core.connect;

import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Build;
import android.util.Log;

import com.magima.core.app.BaseApplication;
import com.magima.core.app.ExceptionConstants;
import com.magima.core.app.ProfilerConstants;

import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.HttpVersion;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.HTTP;
import org.json.JSONObject;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.zip.GZIPInputStream;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;


public class ServerOperator {
	public static final String CODE = "code";
	public static final String RESULT = "result";

    private static final String INTERNET_SECURITY_EXCEPTION_MESSAGE = "missing INTERNET permission";
	
	static private String currentApiName;
	static private int currentApiCount = 0;	
	@SuppressWarnings("unused")
	static private long currentApiTX = 0;
	static private long currentApiRX = 0;
	static private long currentApiTime = 0;
	static int TIMEOUT = 10000;
	
	private static String clientVersion = null;
	private static String getClientVersion(Context context){
		if ( clientVersion == null ) {
			try {
				PackageInfo pinfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
				clientVersion = String.valueOf(pinfo.versionCode);
			} catch (NameNotFoundException e) {
				e.printStackTrace();
			}
		}
		return clientVersion;		
	}
	
	public static ServerResult httpsConnect(Context context, String url, String method, HashMap<String, String> headers, 
			HashMap<String, String> params, String paramsStr, boolean jsonString) {
		return httpsConnect(context, url, method, headers, params, paramsStr, jsonString, TIMEOUT);
	}
	
	public static ServerResult httpsConnect(Context context, String url, String method, HashMap<String, String> headers, 
			HashMap<String, String> params, String paramsStr, boolean jsonString, int socketTimeout) {
		BaseApplication app = BaseApplication.fromContext(context);
		currentApiName = app.getProfilerTool().getInPara(ProfilerConstants.IN_API, currentApiName, false);
		if ( currentApiName == null )
			currentApiName = "NOT_EXIST";
		if ( currentApiName != null && currentApiName.equals(ProfilerConstants.IN_API_TURN_OFF) ) {
			currentApiCount = 0;
			currentApiTime = 0;
			currentApiTX = 0;
			currentApiRX = 0;
			app.getProfilerTool().setOutPara(ProfilerConstants.OUT_API_COUNT, currentApiCount, false);
			app.getProfilerTool().setOutPara(ProfilerConstants.OUT_API_TIME, currentApiTime, false);
			app.getProfilerTool().setOutPara(ProfilerConstants.OUT_API_RX, 0, false);
			app.getProfilerTool().setOutPara(ProfilerConstants.OUT_API_TX, 0, false);
		}
		long startTime = (new Date()).getTime();
		int size = 0;
		
		HttpResponse response = null;
		HttpRequestBase request = null;
		BufferedReader br = null; 
//		HttpClient client = getNewHttpClient(); 
		HttpClient client = new DefaultHttpClient();

		if(method.equals("GET")){
			request = new HttpGet(url);
		}else if(method.equals("PUT")){
			request = new HttpPut(url);
		}else if(method.equals("POST")){
			request = new HttpPost(url);
		}else if(method.equals("DELETE")){
			request = new HttpDelete(url);
		}
		
		if(!method.equals("GET")){
			((DefaultHttpClient)client).setHttpRequestRetryHandler(new DefaultHttpRequestRetryHandler(0, false));
		}
		
		HttpParams p = client.getParams();
		//ConnManagerParams.setTimeout(p, TIMEOUT);
	    HttpConnectionParams.setConnectionTimeout(p, TIMEOUT);
	    HttpConnectionParams.setSoTimeout(p, socketTimeout != 0 ? socketTimeout : TIMEOUT);
		if(headers != null){
			@SuppressWarnings("rawtypes")
			Iterator iter = headers.entrySet().iterator();
			while (iter.hasNext()) {
				@SuppressWarnings("rawtypes")
				Map.Entry entry = (Map.Entry) iter.next();
				String key = (String) entry.getKey();
				String val = (String) entry.getValue();

				request.setHeader(key,val);
			}
		}

		request.setHeader("Client_Version", getClientVersion(context) );
		try {
			if (Build.VERSION.SDK_INT > Build.VERSION_CODES.HONEYCOMB_MR2) {
				//request.setHeader("Connection", "close");
				request.setHeader("Connection", "Keep-Alive");
				request.setHeader("Keep-Alive", "timeout=10000");
			} else {
				//System.setProperty("http.KeepAlive", "false");
				System.setProperty("http.KeepAlive", "true");
			}
		} catch (Exception e ){
			e.printStackTrace();
		}
		request.setHeader("Accept-Encoding", "gzip,deflate");
		
		try {
			String paramsLog = "";
			if(params != null){
				if(jsonString){
					JSONObject jo = new JSONObject(params);
					StringEntity entity = new StringEntity(jo.toString(), HTTP.UTF_8);
					if(method.equals("POST"))
						((HttpPost)request).setEntity(entity); 
					else if(method.equals("PUT"))
						((HttpPut)request).setEntity(entity); 
				}else{
					List<NameValuePair> parameters = new ArrayList<NameValuePair>();  

					@SuppressWarnings("rawtypes")
					Iterator iter = params.entrySet().iterator();
					while (iter.hasNext()) {
						@SuppressWarnings("rawtypes")
						Map.Entry entry = (Map.Entry) iter.next();
						String key = (String) entry.getKey();
						String val = (String) entry.getValue();

						parameters.add(new BasicNameValuePair(key, val));
					}
					
					UrlEncodedFormEntity entity = new UrlEncodedFormEntity(parameters, HTTP.UTF_8);
					if(method.equals("POST"))
						((HttpPost)request).setEntity(entity); 
					else if(method.equals("PUT"))
						((HttpPut)request).setEntity(entity); 
				}
				paramsLog = (new JSONObject(params)).toString();
			}
			
			if(paramsStr != null){
				StringEntity entity = new StringEntity(paramsStr, HTTP.UTF_8);
				if(method.equals("POST"))
					((HttpPost)request).setEntity(entity); 
				else if(method.equals("PUT"))
					((HttpPut)request).setEntity(entity); 
				paramsLog = paramsStr;
			}

			Log.i("getData-------------", method + " : " + url + "\n" + paramsLog);
			
			response = client.execute(request);
			
			int statusCode = response.getStatusLine().getStatusCode();
			Log.i("getData-------------", "url : " + url + ", statusCode : " + statusCode);
			
			ServerResult sr = new ServerResult();
			sr.code = statusCode;
			
			switch (statusCode) {
			case HttpStatus.SC_OK:
			case HttpStatus.SC_INTERNAL_SERVER_ERROR:
				Header contentEncoding = response.getFirstHeader("Content-Encoding");
				if ( contentEncoding != null && contentEncoding.getValue().equalsIgnoreCase("gzip") ){
					Log.i("getData-------------","in gzip");
					BufferedInputStream bis;
					GZIPInputStream is = new GZIPInputStream( bis = new BufferedInputStream(response.getEntity().getContent()));
					StringBuilder stringBuffer = new StringBuilder("");
					char[] buffer = new char[1024];
					int readSize = 0;
					InputStreamReader reader = new InputStreamReader(is, "utf-8");
					while ( ( readSize = reader.read(buffer) ) > 0 ) {
						stringBuffer.append(buffer, 0 , readSize);
					}
					reader.close();
					bis.close();
					is.close();

					sr.result = stringBuffer.toString();
				} else {
					sr.result = org.apache.http.util.EntityUtils.toString(response.getEntity());
				}
				size = sr.result.length();
				Log.i("getData-------------", "<success>  url : " + url + ", size:"+size+",result : " + sr.result);
				break;

			default:
				break;
			}
			
			return sr;
		} catch( ConnectTimeoutException e){
			e.printStackTrace();
			ServerResult sr = new ServerResult();
			sr.code = ExceptionConstants.CODE_TIMEOUT_EXCEPTION;
			sr.result = e.toString();
			return sr;
		} catch( SocketTimeoutException e){
			e.printStackTrace();
			ServerResult sr = new ServerResult();
			sr.code = ExceptionConstants.CODE_TIMEOUT_EXCEPTION;
			sr.result = e.toString();
			return sr;
		} catch ( javax.net.ssl.SSLPeerUnverifiedException e ){
			e.printStackTrace();
			ServerResult sr = new ServerResult();
			sr.code = ExceptionConstants.CODE_SSL_PEER_UNVERIFIED_EXCEPTION;
			sr.result = e.toString();
			return sr;
		} catch(UnknownHostException e){
			e.printStackTrace();
			ServerResult sr = new ServerResult();
			sr.code = ExceptionConstants.CODE_HOST_UNKNOWN_EXCEPTION;
			sr.result = e.toString();
			return sr;
		} catch(ClientProtocolException e){
			e.printStackTrace();
			ServerResult sr = new ServerResult();
			sr.code = ExceptionConstants.CODE_CLIENT_PROTOCOL_EXCEPTION;
			sr.result = e.toString();
			return sr;
		} catch(MalformedURLException e){
			e.printStackTrace();
			ServerResult sr = new ServerResult();
			sr.code = ExceptionConstants.CODE_URL_MALFORMED_EXCEPTION;
			sr.result = e.toString();
			return sr;
		} catch(Exception e) {
			e.printStackTrace();
			ServerResult sr = new ServerResult();
            if(e instanceof SecurityException && e.getMessage() != null && e.getMessage().contains(INTERNET_SECURITY_EXCEPTION_MESSAGE)){
                sr.code = ExceptionConstants.CODE_MISSING_INTERNET_PERMISSION;
            }else {
                sr.code = ExceptionConstants.CODE_EXCEPTION;
            }
			sr.result = e.toString();
			return sr;
		}finally{
			client.getConnectionManager().closeIdleConnections(0, TimeUnit.MILLISECONDS);
			if(br != null){
				try {  
					br.close();  
				} catch (IOException e) {  
					e.printStackTrace();  
				}  
			}
			
			long endTime = (new Date()).getTime();
			if ( currentApiName.equals(ProfilerConstants.IN_API_ALL) || url.contains(currentApiName) ) {
				currentApiCount++;
				currentApiTime += endTime - startTime;
				currentApiRX += size;
				app.getProfilerTool().setOutPara(ProfilerConstants.OUT_API_COUNT, currentApiCount, false);
				app.getProfilerTool().setOutPara(ProfilerConstants.OUT_API_TIME, currentApiTime, false);
				app.getProfilerTool().setOutPara(ProfilerConstants.OUT_API_RX, currentApiRX, false);
			}
		}
	}
	
	public static class ServerResult{
		public int code;
		public String result;
	}
	
	@SuppressWarnings("unused")
	private static HttpClient getNewHttpClient() {  
		try {  
			KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());  
			trustStore.load(null, null);  

			SSLSocketFactory sf = new SSLSocketFactoryEx(trustStore);  
			sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);  

			HttpParams params = new BasicHttpParams();  
			HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);  
			HttpProtocolParams.setContentCharset(params, HTTP.UTF_8);  

			SchemeRegistry registry = new SchemeRegistry();  
			registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));  
			registry.register(new Scheme("https", sf, 443));  

			ClientConnectionManager ccm = new ThreadSafeClientConnManager(params, registry);  

			return new DefaultHttpClient(ccm, params);  
		} catch (Exception e) {  
			return new DefaultHttpClient();  
		}  
	}

	private static class SSLSocketFactoryEx extends SSLSocketFactory {

		SSLContext sslContext = SSLContext.getInstance("TLS");

		public SSLSocketFactoryEx(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
			super(truststore);

			TrustManager tm = new X509TrustManager() {
				@Override
				public java.security.cert.X509Certificate[] getAcceptedIssuers() {
					return null;
				}
				@Override
				public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws java.security.cert.CertificateException {

				}
				@Override
				public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws java.security.cert.CertificateException {

				}
			};
			sslContext.init(null, new TrustManager[] { tm }, null);
		}

		@Override
		public Socket createSocket(Socket socket, String host, int port,boolean autoClose) throws IOException, UnknownHostException {
			return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose);
		}

		@Override
		public Socket createSocket() throws IOException {
			return sslContext.getSocketFactory().createSocket();
		}
	}
}
